package webfluxclient.proxy;


import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import webfluxclient.ApiServer;
import webfluxclient.bean.MethodInfo;
import webfluxclient.bean.ServerInfo;
import webfluxclient.handler.RestHandlerImpl;
import webfluxclient.interfaces.ProxyCreator;
import webfluxclient.interfaces.RestHandler;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.LinkedHashMap;
import java.util.Map;

@Slf4j
public class JDKProxyCreator implements ProxyCreator {

    @Override
    public Object createProxy(Class<?> type) {
        log.info("createProxy:{}", type);

        //根据接口得到APi服务器信息
        ServerInfo serverInfo = extractServerInfo(type);
        log.info("serverInfo:{}", serverInfo);

        //给每一个代理一个实现
        RestHandler handler = new RestHandlerImpl();

        handler.init(serverInfo);


        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{type}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //根据方法和参数调用信息
                        MethodInfo methodInfo = extractMethodInfo(method, args);
                        log.info("methodInfo:{}", methodInfo);
                        //调用rest
                        return handler.invokeRest(serverInfo, methodInfo);
                    }

                    /**
                     * 根据方法定义和调用参数得到调用的相关信息
                     * @param method
                     * @param args
                     * @return
                     */
                    private MethodInfo extractMethodInfo(Method method, Object[] args) {

                        MethodInfo methodInfo = new MethodInfo();
                        extractUrlAndMethod(method, methodInfo);
                        extractRequestParamAndBody(method, args, methodInfo);

                        //提取返回对象的信息
                        extractReturnInfo(method, methodInfo);
                        return methodInfo;
                    }

                    private void extractReturnInfo(Method method, MethodInfo methodInfo) {
                        //返回flux还是monno
                        //isAssignableFrom 判断类型是否是某个子类
                        boolean isFlux = method.getReturnType().isAssignableFrom(Flux.class);
                        methodInfo.setReturnFlux(isFlux);

                        //得到返回对象的时机类型
                        Class<?> elementType = extractElementType(method.getGenericReturnType());
                        methodInfo.setReturnElementType(elementType);
                    }


                });
    }

    /**
     * 得到泛型类型的实际类型
     *
     * @param genericReturnType
     * @return
     */
    private Class<?> extractElementType(Type genericReturnType) {
        Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
        return (Class<?>) actualTypeArguments[0];
    }

    /**
     * 得到请求的param和body
     *
     * @param method
     * @param args
     * @param methodInfo
     */
    private void extractRequestParamAndBody(Method method, Object[] args, MethodInfo methodInfo) {
        Parameter[] parameters = method.getParameters();
        Map<String, Object> params = new LinkedHashMap<>();

        for (int i = 0; i < parameters.length; i++) {
            PathVariable pathVariable = parameters[i].getAnnotation(PathVariable.class);
            if (pathVariable != null) {
                params.put(pathVariable.value(), args[i]);
            }
            //带了requestBody
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                methodInfo.setBody((Mono<?>) args[i]);
                methodInfo.setBodyElementType(extractElementType(parameters[i].getParameterizedType()));
            }
        }
        methodInfo.setParams(params);
    }

    /**
     * 得到请求的请求方式和url
     *
     * @param method
     * @param methodInfo
     */
    private void extractUrlAndMethod(Method method, MethodInfo methodInfo) {
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
            //get
            if (annotation instanceof GetMapping) {
                GetMapping a = (GetMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.GET);
            } else if (annotation instanceof PostMapping) {
                PostMapping a = (PostMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.POST);
            } else if (annotation instanceof PutMapping) {
                PutMapping a = (PutMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.PUT);
            } else if (annotation instanceof DeleteMapping) {
                DeleteMapping a = (DeleteMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.DELETE);
            }
        }
    }

    /**
     * 提取服务器信息
     *
     * @param type
     * @return
     */
    private ServerInfo extractServerInfo(Class<?> type) {
        ServerInfo serverInfo = new ServerInfo();
        ApiServer anno = type.getAnnotation(ApiServer.class);
        serverInfo.setUrl(anno.value());
        return serverInfo;

    }


}
